﻿namespace GoDiscover
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel.Composition;
	using System.ComponentModel.Composition.Hosting;
	using System.Diagnostics;
	using System.Linq;

	public interface IDiscoverableParent
	{
	}

	public interface IDiscoverableChild
	{
	}

	public interface IDiscoverableParent<TSelf> : IDiscoverableParent
		where TSelf : IDiscoverableParent<TSelf>
	{
	}

	[InheritedExport]
	public interface IDiscoverableRoot : IDiscoverableParent
	{
	}

	public interface IDiscoverableRoot<TSelf> : IDiscoverableParent<TSelf>, IDiscoverableRoot
		where TSelf : IDiscoverableRoot<TSelf>
	{
	}

	[InheritedExport]
	public interface IDiscoverableChild<TParent> : IDiscoverableChild
		where TParent : IDiscoverableParent<TParent>
	{
	}

	public abstract class DiscoverableParentBase<TSelf> : IDiscoverableParent<TSelf>, IPartImportsSatisfiedNotification
		where TSelf : IDiscoverableParent<TSelf>
	{
		/// <summary>
		/// Gets or sets the container.
		/// </summary>
		/// <value>
		/// The container.
		/// </value>
		[Import]
		public virtual CompositionContainer Container { get; set; }

		/// <summary>
		/// Called when a part's imports have been satisfied and it is safe to use.
		/// </summary>
		public virtual void OnImportsSatisfied()
		{
			Debug.Assert(this.Container != null, "An instance of the MEF CompositionContainer containing the exports for the GoDiscover framework must be registered within the CompositionContainer itself.");

			if (this.Container == null)
			{
				throw new InvalidOperationException("An instance of the MEF CompositionContainer containing the exports for the GoDiscover framework must be registered within the CompositionContainer itself.");
			}

			var children = this.Container.GetExportedValues<IDiscoverableChild<TSelf>>();

			this.OnAfterImportsSatisfied(children);
		}

		/// <summary>
		/// Called when [after imports satisfied].
		/// </summary>
		/// <param name="children">The children.</param>
		protected abstract void OnAfterImportsSatisfied(IEnumerable<IDiscoverableChild<TSelf>> children);
	}

	public abstract class DiscoverableParent<TSelf> : DiscoverableParentBase<TSelf>
		where TSelf : IDiscoverableParent<TSelf>
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="DiscoverableParent&lt;TSelf&gt;"/> class.
		/// </summary>
		public DiscoverableParent()
		{
			this.Children = new List<IDiscoverableChild<TSelf>>();
		}

		/// <summary>
		/// Gets or sets the children.
		/// </summary>
		/// <value>
		/// The children.
		/// </value>
		public virtual IEnumerable<IDiscoverableChild<TSelf>> Children { get; set; }

		/// <summary>
		/// Gets the first child in the collection of children that matches the specified type.
		/// </summary>
		/// <typeparam name="TChild">The type of the child.</typeparam>
		/// <returns>The child of the specified <typeparamref name="TChild"/> type.</returns>
		public TChild FirstChildOf<TChild>()
		{
			return this.Children.OfType<TChild>().FirstOrDefault();
		}

		/// <summary>
		/// Called when [after imports satisfied].
		/// </summary>
		/// <param name="children">The children.</param>
		protected override void OnAfterImportsSatisfied(IEnumerable<IDiscoverableChild<TSelf>> children)
		{
			this.Children = children;
		}
	}

	public abstract class DiscoverableRoot<TSelf> : DiscoverableParent<TSelf>, IDiscoverableRoot<TSelf>
		where TSelf : DiscoverableRoot<TSelf>, IDiscoverableParent<TSelf>, IDiscoverableRoot<TSelf>
	{
	}

	public abstract class DiscoverableChild<TParent, TSelf> : DiscoverableParent<TSelf>, IDiscoverableChild<TParent>
		where TParent : IDiscoverableParent<TParent>
		where TSelf : IDiscoverableParent<TSelf>
	{
	}

	public static class DiscoverableExtensions
	{
		/// <summary>
		/// Gets the first object in the collection that matches the specified type.
		/// </summary>
		/// <typeparam name="TType">The type of the type.</typeparam>
		/// <param name="rootItems">The root items.</param>
		/// <returns>The first object in the collection that matches the specified type.</returns>
		public static TType FirstOf<TType>(this IEnumerable<IDiscoverableRoot> rootItems)
		{
			return rootItems.OfType<TType>().FirstOrDefault();
		}
	}
}